/*
 * This file is part of Space Invaders: Rehash
 * Copyright (C)2007 Vicent Marti (tanoku@gmail.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>

#include "SDL.h"
#include "SDL_ttf.h"
#include "SDL_opengl.h"

#include "main.h"
#include "gfx.h"
#include "font_handler.h"
#include "game.h"
#include "sprites.h"
#include "game_player.h"
#include "game_missile.h"
#include "game_alien.h"
#include "game_const.h"

extern std::string level_passwords[];
extern int g_Options;

Game::Game( int gamerules, int level )
{
	m_gameRules = gamerules;
	m_keymap = KEYMAP_NORMAL;
	m_finished = false;
	m_init = false;
	m_score = 0;
	m_scoreGfx = 0;
	
	m_placingBarrier = 0;
	m_barrierCount = 0;
	
	m_pauseSelect = 0;
	
	if ( m_gameRules & GR_STATIC_DEFENCES )
	{
		for ( int i=0; i<3; ++i )
		{
			m_barrierX[i] = (i+1)*(SCREEN_W/4);
			m_barrierHealth[i] = 100;
		}
		m_barrierCount = 3;

		startCountdown();
	}
	else if ( (m_gameRules & GR_PLACE_DEFENCES_4) || (m_gameRules & GR_PLACE_DEFENCES_2) )
	{
		m_status = STATUS_PLACING;
		m_barrierCount = 4;
		
		if (m_gameRules & GR_PLACE_DEFENCES_2)
			m_barrierCount = 2;
		
		for ( int i=0; i<m_barrierCount; ++i )
		{
			m_barrierX[i] = SCREEN_W/2;
			m_barrierHealth[i] = 100;
		}
	}
	else startCountdown();
	
	m_lastPause = m_lastPlace = SDL_GetTicks();
	m_canPause = true;
	
	m_level = level;
	
	m_lastPauseKey = 0;
}

void Game::init()
{
	m_init = true;
	
	generateEnemies();
	
	m_player = new Player((Game*)this);
	
	m_gamet.start();
	m_EM_return = false;
	m_EM_x = 32;
	m_EM_y = 16;

	Mix_HaltMusic();
}

void Game::generateEnemies()
{
	int enemyCount = 0, row = 0, col = 0, enemy_id = 0, max_cols = ENEMIES_MAX_COLS;

	if ( m_gameRules & GR_BOSSFIGHT_1 )
	{
		Alien *newAlien = new Alien(this, 0, 0, 0, -42);
		if ( newAlien )
		{
			m_enemies.push_back(newAlien);
		}
		return;
	}
	
	if (m_gameRules & GR_LOW_ENEMY_COUNT)
	{
		enemyCount = 12;
	}
	else if ( m_gameRules & GR_MED_ENEMY_COUNT )
	{
		enemyCount = 24;
	}
	else if ( m_gameRules & GR_HIGH_ENEMY_COUNT )
	{
		enemyCount = 36;
	}
	
	if (m_gameRules & GR_PRIMESHOOTER)
	{
		enemyCount = enemyCount / 2 - (int)(enemyCount/10);
		max_cols--;
	}
	
	for ( int i = 0; i < enemyCount; ++i )
	{
		if (m_gameRules & GR_PRIMESHOOTER)
		{
			if (m_gameRules & GR_PRIMES_MAX_20)
				enemy_id = 2+rand()%19;
			else if (m_gameRules & GR_PRIMES_MAX_100)
				enemy_id = 2+rand()%99;
			else if (m_gameRules & GR_PRIMES_MAX_500)
				enemy_id = 2+rand()%499;
			else if (m_gameRules & GR_PRIMES_MAX_1000)
				enemy_id = 2+rand()%999;
		}
		else if ( m_gameRules & GR_COLORSHOOTER )
		{
			if (m_gameRules & GR_4_COLORS)
				enemy_id = 1<<(1+rand()%4);
			else if (m_gameRules & GR_6_COLORS)
				enemy_id = 1<<(1+rand()%6);
			else if (m_gameRules & GR_8_COLORS)
				enemy_id = 1<<(1+rand()%8);
		}
		
		Alien *newAlien = new Alien(this, row, col, (row%2)*2, enemy_id );
		if ( newAlien )
		{
			m_enemies.push_back(newAlien);
		}
		
		col++; 
		if ( col > max_cols )
		{
			col =0; row++;
		}
	}
}

void Game::translateColor( int color, float &r, float &g, float &b )
{
	switch ( color )
	{
	case COLOR_RED:         r = 0.9f; g = 0.0f; b = 0.0f; break;
	case COLOR_BLUE:        r = 0.0f; g = 0.0f; b = 0.9f; break;
	case COLOR_GREEN:       r = 0.0f; g = 0.9f; b = 0.0f; break;
	case COLOR_YELLOW:      r = 0.9f; g = 0.9f; b = 0.0f; break;
	case COLOR_PURPLE:      r = 0.9f; g = 0.0f; b = 0.9f; break;
	case COLOR_BROWN:       r = 139.0f/255.0f; g = 105.0f/255.0f; b = 20.0f/255.0f; break;
	case COLOR_ORANGE:      r = 1.0f; g = 0.5f; b = 0.0f; break;
	case COLOR_GREY:        r = 0.25f; g = 0.25f; b = 0.25f; break;
	default:                r = 1.0f; g = 1.0f; b = 1.0f; break;
	}
}

int Game::translateKeyPress( int key )
{
	if (m_gameRules & GR_PRIMESHOOTER)
	{
		if ( g_Options & OPTION_KEYMAP_COMPACT )
		{
			switch ( key )
			{
			case K1: return -1;
			case K2: return 2;
			case K3: return 3;
			case K4: return 5;
			case K5: return 7;
			case K6: return 11;
			case K7: return 13;
			case K8: return 17;
			case K9: return 19;
			default: return 0;
			}       
		}
		else 
		{
			switch ( key )
			{
			case K1: return -1;
			case K2: return 2;
			case K3: return 3;
			case K5: return 5;
			case K7: return 7;
			case KQ: return 11;
			case KW: return 13;
			case KE: return 17;
			case KR: return 19;
			default: return 0;
			}
		}
	}
	else if (m_gameRules & GR_COLORSHOOTER)
	{
		switch ( key )
		{
		case K1: return COLOR_RED;
		case K2: return COLOR_BLUE;
		case K3: return COLOR_GREEN;
		case K4: return COLOR_YELLOW;
		case K5: return COLOR_PURPLE;
		case K6: return COLOR_BROWN;
		case K7: return COLOR_ORANGE;
		case K8: return COLOR_GREY;
		default: return COLOR_NONE;
		}
	}

	return 0;
}

void Game::getEnemyPosition( int row, int col, int &x, int &y )
{
	if ( row > ENEMIES_MAX_ROWS || col > ENEMIES_MAX_COLS ) return;
	int w = 8;
	int h = -5;

	if ( m_bossTimer.get_ticks() > 0 )
	{
		float progress = (float)m_bossTimer.get_ticks()/2000.0f;

		if ( progress < 1.0f )
		{
			x = m_EM_x + col * (ALIEN_W+w) + ALIEN_W/2;
			y = m_EM_y + row * (ALIEN_H+h) + ALIEN_H/2 + (int)(progress*(225));
		}
	}
	else
	{
		if (m_gameRules & GR_PRIMESHOOTER)
		{
			w=20; h=22;
		}

		x = m_EM_x + col * (ALIEN_W+w) + ALIEN_W/2;
		y = m_EM_y + row * (ALIEN_H+h) + ALIEN_H/2;
	}
}

void Game::tick()
{
	if ( !m_init )
	{
		init();
		return;
	}
	
	if ( g_Keys[KCHEAT] )
	{
		m_endgamet.start();
		m_status = STATUS_WIN;
		m_canPause = false;
	}
	
	render();
	
	if ( m_status == STATUS_GAMEOVER || m_status == STATUS_WIN )
	{
		if ( m_endgamet.get_ticks() > 7500 )
		{
			m_finished = true;
			return;
		}		
	}

	if ( m_player->isDead() && m_status != STATUS_GAMEOVER )
	{
		m_endgamet.start();
		m_status = STATUS_GAMEOVER;
	}
	else if ( m_enemies.size() == 0 && m_status != STATUS_WIN )
	{
		m_endgamet.start();
		m_status = STATUS_WIN;
	}

	if ( !g_Keys[KPAUSE] )
			m_canPause = true;

	if ( m_canPause && g_Keys[KPAUSE] && ( m_status == STATUS_PLAYING ||
	     m_status == STATUS_PAUSE ) &&  m_lastPause + GAME_PAUSE_TIME < SDL_GetTicks() )
	{
		if (  m_status == STATUS_PAUSE )
		{
			m_status = STATUS_PLAYING;
		}
		else 
		{
			m_status = STATUS_PAUSE;
			m_pauseSelect = 0;
		}

		m_lastPause = SDL_GetTicks();
		m_canPause = false;
		m_pauseGfx = 0.0f;
	}
	
	if ( m_status == STATUS_COUNTDOWN && m_coundownStart + 3000 < SDL_GetTicks() )
	{
		m_canPause = true;
		m_status = STATUS_PLAYING;
		return;
	}
	
	if ( m_status == STATUS_PAUSE || m_status == STATUS_COUNTDOWN )
	{
		if ( m_status == STATUS_PAUSE && m_lastPauseKey + 150 < SDL_GetTicks() )
		{
			m_lastPauseKey = SDL_GetTicks();
			if ( g_Keys[KSHOOT] )
			{
				if ( m_pauseSelect == 0 )
				{
					m_status = STATUS_PLAYING;
					m_lastPause = SDL_GetTicks();
					m_canPause = false;
					m_pauseGfx = 0.0f;
				}
				else
				{
					m_status = STATUS_GAMEOVER;
					m_finished = true;
				}
			}
			else if ( g_Keys[KUP] || g_Keys[KDOWN] )
			{
				m_pauseSelect = !m_pauseSelect;
			}
		}
		
		renderStatus();
		return;
	}
	else if ( m_status == STATUS_PLACING )
	{
		if ( g_Keys[KLEFT] )
		{
			m_barrierX[m_placingBarrier] -= ALIEN_W/4;
			if ( m_barrierX[m_placingBarrier] < 14 + BARRIER_W/2 )
				m_barrierX[m_placingBarrier] = 14 + BARRIER_W/2 ;
		}
		
		if ( g_Keys[KRIGHT] )
		{
			m_barrierX[m_placingBarrier] += ALIEN_W/4;
			if ( m_barrierX[m_placingBarrier] > SCREEN_W - 14 - BARRIER_W/2  )
				m_barrierX[m_placingBarrier] = SCREEN_W - 14 - BARRIER_W/2 ;
		}
		
		if ( g_Keys[KSHOOT] && m_lastPlace + 500 < SDL_GetTicks() )
		{
			for ( int i=0; i<m_placingBarrier; ++i )
			{
				if ( abs(m_barrierX[m_placingBarrier] - m_barrierX[i]) < BARRIER_W )
					return;
			}
			
			m_lastPlace = SDL_GetTicks();
			m_placingBarrier++;
			
			if ( m_placingBarrier == m_barrierCount )
			{
				startCountdown();
			}
		}
		
		return;
	}

	if ( m_status != STATUS_GAMEOVER && m_status != STATUS_WIN && !m_bossTimer.get_ticks() )
	{
		int mov_ticks = 750;
		
		if ( m_gameRules & GR_FAST_ENEMIES )
			mov_ticks = 350;
		
		if ( m_gamet.get_ticks() > mov_ticks )
		{
			Mix_PlayChannel( -1, g_Sounds[SOUND_INVADER1], 0 );
			m_EM_x += (m_EM_return)?-16:16;
			
			if ( m_EM_x < 32 )
			{
				m_EM_x = 32; 
				m_EM_return = false;
				m_EM_y += 16;
			}
			else if ( m_EM_x > SCREEN_W/2  )
			{
				m_EM_x = SCREEN_W/2;
				m_EM_y += 16;
				m_EM_return = true;
			}
			
			m_gamet.start();
		}
	}
	
	m_player->tick();

	for ( int i=0; i<(int)m_enemies.size(); ++i )
	{
		if ( m_enemies[i]->isDead() )
		{
//			Mix_PlayChannel( -1, g_Sounds[SOUND_ALIENDEAD], 0 );
			m_score += 100;
			delete m_enemies[i];
			m_enemies.erase(m_enemies.begin() + i);
			--i;
		}
		else 
		{
			m_enemies[i]->tick();
		}
	}
	
	renderSprites();

	if ( m_status == STATUS_GAMEOVER || m_status == STATUS_WIN )
		renderStatus();
}

void Game::onEvent( SDL_Event *event )
{	
}

bool Game::getBarrierBounds( int barrier, SDL_Rect *r )
{
	if ( barrier < 0 || barrier > m_barrierCount )
		return false;

	if ( m_barrierHealth[barrier] <= 0 )
		return false;
	
	r->x = m_barrierX[barrier] - BARRIER_W/2;
	r->y = SCREEN_H - BARRIER_ABS_H - BARRIER_H/2;
	r->w = BARRIER_W;
	r->h = BARRIER_H;
	return true;
}

void Game::barrierHit( int barrier, int damage )
{
	if ( m_barrierHealth[barrier] <= 0 )
		return;

	m_barrierHealth[barrier] -= damage;

	if ( m_barrierHealth[barrier] <= 0 )
	{
		printf("Barrier down!");
		for ( int i=0; i<4; ++i )
		{
			newSprite( SPRITE_EXPLOSION, m_barrierX[barrier] - BARRIER_W/2 + i*16,	SCREEN_H - BARRIER_ABS_H + (i%2?2:-2) );
		}
	}
}

void Game::render()
{		
	glEnable2D();

	glLoadIdentity();
	glTranslatef( 0.0f, 0.0f, -1.0f );
	
	glColor4f( 1, 1, 1, 1.0 );
	glBindTexture(GL_TEXTURE_2D, g_Textures[4]);
	
	glBegin( GL_QUADS );
		glTexCoord2f( 0.0f, 1.0f ); glVertex3f( 0.0f,		0.0f,		0.0f );
		glTexCoord2f( 1.0f, 1.0f ); glVertex3f( SCREEN_W,	0.0f,		0.0f );
		glTexCoord2f( 1.0f, 0.0f ); glVertex3f( SCREEN_W,	SCREEN_H,	0.0f );
		glTexCoord2f( 0.0f, 0.0f ); glVertex3f( 0.0f,		SCREEN_H,	0.0f );
	glEnd();
	
	glBindTexture (GL_TEXTURE_2D, 0);
	
	SDL_Rect r;
	
	if (m_status==STATUS_PLACING)
	{
		for ( int i=0; i<=m_placingBarrier; ++i )
		{
			glDrawRect(m_barrierX[i]-BARRIER_W/2 , SCREEN_H - BARRIER_ABS_H - BARRIER_H/2 , BARRIER_W, BARRIER_H, 1, 0, 0);
		}
		
		
		char *text1 = "PRESS (LEFT ARROW) OR (RIGHT ARROW) TO PLACE YOUR DEFENCES", text2[32];
		sprintf(text2, "%d BARRIERS LEFT", m_barrierCount-m_placingBarrier );

		g_Fonts->get(FONT_TEXT)->textSize( text1, &r );
		glPrint(FONT_TEXT, (SCREEN_W/2) - (r.w/2), SCREEN_H - 15 - 3 - 2*r.h, text1);

		g_Fonts->get(FONT_TEXT)->textSize(text2, &r );
		glPrint(FONT_TEXT, (SCREEN_W/2) - (r.w/2), SCREEN_H - 15 - r.h, text2);
	}
	else if ( m_status == STATUS_PLAYING )
	{
		char text[32];
		sprintf(text, "%d%% HP   %d POINTS", m_player->getHealth(), m_score);
		g_Fonts->get(FONT_TEXT)->textSize(text, &r );
		glPrint(FONT_TEXT, SCREEN_W/2 - (r.w/2), 16, text);
		
		if ( m_barrierCount > 0 )
		{
			SDL_Rect r;
			for ( int i=0; i<m_barrierCount; ++i )
			{
				if ( m_barrierHealth[i] > 0 )
				{
					sprintf(text, "%d%%", m_barrierHealth[i]);
					glDrawTexuredRect(m_barrierX[i]-BARRIER_W/2 , SCREEN_H - BARRIER_ABS_H - BARRIER_H/2, BARRIER_W, BARRIER_H, g_Textures[15], 1, 1, 1);
					g_Fonts->get(FONT_TEXT)->textSize( text, &r );
					glPrint(FONT_TEXT, m_barrierX[i] - r.w/2, SCREEN_H - BARRIER_ABS_H + BARRIER_H/2, text);
				}
			}
		}
	}

	glDrawRect(0, 0, 8, SCREEN_H, 1, 1, 1);
	glDrawRect(0, 0, SCREEN_W, 8, 1, 1, 1);
	glDrawRect(SCREEN_W-8, 0, 8, SCREEN_H, 1, 1, 1);
	glDrawRect(0, SCREEN_H-8, SCREEN_W, 8, 1, 1, 1);
	glDisable2D();
}

void Game::renderStatus()
{
	SDL_Rect r;
	glEnable2D();
	if ( m_status == STATUS_PAUSE )
	{
		glDrawRect(SCREEN_W/2 - 128, SCREEN_H/2 - 64, 256, 128, 1, 1, 1);
		glDrawRect(SCREEN_W/2 - 128 + 4, SCREEN_H/2 - 64 + 4, 256-8, 128-8, 0, 0, 0);
		
		g_Fonts->get(FONT_TITLE)->textSize( "PAUSED", &r );
		glPrint(FONT_TITLE, SCREEN_W/2 - (r.w/2), SCREEN_H/2 - 64 + 16, "PAUSED");
		
		if ( m_pauseSelect == 0 )
		{
			g_Fonts->get(FONT_ALIEN)->textSize( "* CONTINUE *", &r );
			glPrint(FONT_ALIEN, SCREEN_W/2 - (r.w/2), SCREEN_H/2 - 64 + 64, "* CONTINUE *");
		
			g_Fonts->get(FONT_ALIEN)->textSize( "QUIT", &r );
			glPrint(FONT_ALIEN, SCREEN_W/2 - (r.w/2), SCREEN_H/2 - 64 + 96, "QUIT");
		}
		else
		{
			g_Fonts->get(FONT_ALIEN)->textSize( "CONTINUE", &r );
			glPrint(FONT_ALIEN, SCREEN_W/2 - (r.w/2), SCREEN_H/2 - 64 + 64, "CONTINUE");
		
			g_Fonts->get(FONT_ALIEN)->textSize( "* QUIT *", &r );
			glPrint(FONT_ALIEN, SCREEN_W/2 - (r.w/2), SCREEN_H/2 - 64 + 96, "* QUIT *");
		}
	}
	else if ( m_status == STATUS_COUNTDOWN )
	{
		glDrawRect(SCREEN_W/2 - 128, SCREEN_H/2 - 32, 256, 64, 1, 1, 1);
		glDrawRect(SCREEN_W/2 - 128 + 4, SCREEN_H/2 - 32 + 4, 256-8, 64-8, 0, 0, 0);
		int cdtime = SDL_GetTicks() - m_coundownStart; 
		
		if ( cdtime > 2000 )
		{
			g_Fonts->get(FONT_TITLE)->textSize( "GO!", &r );
			glPrint(FONT_TITLE, (SCREEN_W/2) - (r.w/2), SCREEN_H/2 - 32 + 16, "GO!");
		}
		else if ( cdtime > 1000 )
		{
			g_Fonts->get(FONT_TITLE)->textSize( "STEADY...", &r );
			glPrint(FONT_TITLE, (SCREEN_W/2) - (r.w/2), SCREEN_H/2 - 32 + 16, "STEADY...");
		}
		else 
		{
			g_Fonts->get(FONT_TITLE)->textSize( "READY.", &r );
			glPrint(FONT_TITLE, (SCREEN_W/2) - (r.w/2), SCREEN_H/2 - 32 + 16, "READY.");
		}
	}
	else if ( m_status == STATUS_GAMEOVER )
	{
		glDrawRect(SCREEN_W/2 - 128, SCREEN_H/2 - 32, 256, 64, 1, 1, 1);
		glDrawRect(SCREEN_W/2 - 128 + 4, SCREEN_H/2 - 32 + 4, 256-8, 64-8, 0, 0, 0);
		glPrint(FONT_TITLE, SCREEN_W/2 - 128 + 16, SCREEN_H/2 - 32 + 16, "GAME OVER");
	}
	else if ( m_status == STATUS_WIN )
	{
		glDrawRect(SCREEN_W/2 - 256, SCREEN_H/2 - 128, 512, 256, 1, 1, 1);
		glDrawRect(SCREEN_W/2 - 256 + 4, SCREEN_H/2 - 128 + 4, 512-8, 256-8, 0, 0, 0);
		
		if ( m_scoreGfx < m_score )
			m_scoreGfx = (m_scoreGfx + 100)<=m_score?(m_scoreGfx + 100):m_score;
		
		g_Fonts->get(FONT_TITLE)->textSize( "MISSION COMPLETE", &r );
		glPrint(FONT_TITLE, (SCREEN_W/2) - (r.w/2), SCREEN_H/2 - 128 + 16, "MISSION COMPLETE");
		glPrintf(FONT_ALIEN, SCREEN_W/2 - 256 + 32, SCREEN_H/2 - 128 + 64, "SCORE: %d", m_scoreGfx );
		glPrintf(FONT_ALIEN, SCREEN_W/2 - 256 + 32, SCREEN_H/2 - 128 + 96, "BARRIERS LEFT: %d", m_barrierCount);
		glPrintf(FONT_ALIEN, SCREEN_W/2 - 256 + 32, SCREEN_H/2 - 128 + 128, "LIFE LEFT: %d", m_player->getHealth() );
		if ( m_level != -1 )
			glPrintf(FONT_ALIEN, SCREEN_W/2 - 256 + 32, SCREEN_H/2 - 128 + 200, "LEVEL PASSWORD UNLOCKED: %s", level_passwords[m_level].c_str() );
		
		
	}
	glDisable2D();
}

void Game::renderSprites()
{
	glEnable2D();
	for ( int i=0; i<(int)m_sprites.size(); ++i )
	{
		if ( m_sprites[i]->isDead() )
		{
			delete m_sprites[i];
			m_sprites.erase(m_sprites.begin() + i);
			i--;
		}
		else m_sprites[i]->render();
	}
	glDisable2D();
}

void Game::newSprite( int type, int m_x, int m_y, int size )
{
	if ( g_Options & OPTION_NOSPRITES )
		return;
		
	Sprite *newspr = NULL;
	switch( type )
	{
		case SPRITE_EXPLOSION:
			newspr = new Explosion( 6, 12, 4, 4, m_x, m_y, 64, 1250 );
			Mix_PlayChannel( -1, g_Sounds[SOUND_PLAYERDEAD], 0 );
			break;
		
		case SPRITE_SMOKE:
			newspr = new Smoke( 7, 14, 4, 4, m_x, m_y, 32, 500 + rand()%200, true);
			break;
		
		case SPRITE_SMOKETRAIL:
			newspr = new Smoke( 7, 14, 4, 4, m_x, m_y, size, 750 + rand()%500, true);
			break;
			
		case SPRITE_EXPLOSION_SMALL:
			newspr = new Explosion( 6, 12, 4, 4, m_x, m_y, 32, 1250 );
			Mix_PlayChannel( -1, g_Sounds[SOUND_PLAYERDEAD], 0 );
			break;
			
		default:
			return;
	}
	
	if ( newspr )
	{
		m_sprites.push_back((Sprite*)newspr);
	}
}


